##############
# (*) Kaleidoscope
# By: Allan Spale
# June 2008

import sys
import uuid

from Kaleidoscope.KSColumn import KSColumn
from Kaleidoscope.KSColumnAdapted import KSColumnAdapted
from Kaleidoscope.KSEventManager import KSEventManagerError
from Kaleidoscope.KSCursorError import KSCursorError


class KSColumnPlusIndexError(Exception):
    """
    Exception class for KSColumnPlusIndex

    """
    # REDO ALL CONSTANT PROPERTIES TO BE NORMAL/WRITABLE OUTSIDE OF CLASS...
    # FIX THIS LATER

    B_UNKNOWN_EXCEPTION = True
    B_CLASS_EXCEPTION = False


    def __init__(self, u_message):
        self.__u_message = u_message
    def __str__(self):
        return repr(self.__u_message)

    @staticmethod
    def print_and_raise_error(u_message, b_unknown_exception):
        """
        Convenience method for printing and raising an error for this
        particular Exception subclass.

        """
        u_message = u'KSColumnPlusIndex.py: ' + u_message
        sys.stderr.write(u_message + u'\n')

        if (b_unknown_exception):
            raise Exception(u_message)
        else:
            raise KSColumnPlusIndexError(u_message)


class KSColumnPlusIndex(KSColumnAdapted):
    """
    This is a dual-column solution to allow for fast key search and
    fast value search. So, one column is the traditional key-value arrangement.
    The other column is a value-key arrangement where the value is the key
    and the key is the value. No assumption will be made as to whether or not
    the value column is actually linked to another column. Additionally, the
    data that will be indexed will have to be practically searchable in nature
    (numerical or alphabetical). It may not make sense to search on an image,
    audio file, or video file.

    Because of the high likelihood of duplicate values for the inverse index,
    the index column must be an object of a class that supports duplicate
    values. Because of the generic-ness of this class, this will have to be
    enforced by the programmer.

    """
    @property
    def is_open(self):
        return self.__b_open

    @property
    def ksc_index_column(self):
        """Bad idea...but it is necessary in KSSysColumn for now."""
        return self.__ksc_index_column


    def __init__(self, ksc_primary_column, ksc_index_column):
        self.__b_open = False

        if (isinstance(ksc_primary_column, KSColumn)):
            self.__ksc_primary_column = ksc_primary_column
            #print 'primary column = ', type(ksc_primary_column)
        else:
            KSColumnPlusIndexError.print_and_raise_error(u'The object '
                + u'provided by ksc_primary_column is not of type KSColumn.',
                KSColumnPlusIndexError.B_CLASS_EXCEPTION)

        if (isinstance(ksc_primary_column, KSColumn)):
            self.__ksc_index_column = ksc_index_column
        else:
            KSColumnPlusIndexError.print_and_raise_error(u'The object '
                + u'provided by ksc_index_column is not of type KSColumn.',
                KSColumnPlusIndexError.B_CLASS_EXCEPTION)

        self.__id = uuid.uuid4()
        self.get_transaction = self.__ksc_primary_column.get_transaction


    def __del__(self):
        """
        Upon deletion of the class instance, close the column files.

        """
        self.close()


    # MANUALLY OPEN AND CLOSE FILES BECAUSE OF COLUMN REUSE IN OTHER CLASSES
    def open(self, i_flags, i_permissions, kstx_parent_transaction):
        """
        Open the files containing the columns.

        """
        try:
            if not (self.__b_open):
                kstx_transaction = self.get_transaction(kstx_parent_transaction,
                    self.open)
                self.__ksc_primary_column.open(
                    i_flags, i_permissions, kstx_transaction)
                self.__ksc_index_column.open(
                    i_flags, i_permissions, kstx_transaction)
                kstx_transaction.commit(0, self.open)
                self.__b_open = True
            return self.__b_open
        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The files related '
                + u'to the columns could not be opened. ',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def close(self):
        """
        Close the file containing the column.

        """
        try:
            if (self.__b_open):
                self.__ksc_primary_column.close()
                self.__ksc_index_column.close()
                self.__b_open = False
            return self.__b_open
        except Exception:
            KSColumnPlusIndexError.print_and_raise_error(u'The files related '
                + u'to the columns could not be closed. ',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def get_data_from_key(self, obj_key, kstx_parent_transaction,
                              b_return_packed_data):
        """
        Return the data object based on an integer or string key. This lookup
        will simply rely on the primary_column.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.get_data_from_key)
            obj_data = self.__ksc_primary_column.get_data_from_key(
                obj_key, kstx_parent_transaction, b_return_packed_data)
            kstx_transaction.commit(0, self.get_data_from_key)
            return obj_data

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The data from the '
                + u'keys could not be retrieved due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def get_keys_from_data_value(self, obj_target_data, kstx_parent_transaction,
                                     b_return_packed_data):
        """
        Use the index column to retrieve the keys.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.get_keys_from_data_value)
            list_keys = self.__ksc_index_column.get_data_from_key(
                obj_target_data, kstx_parent_transaction, b_return_packed_data)
            kstx_transaction.commit(0, self.get_keys_from_data_value)
            return list_keys

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The keys from the '
                + u'column could not be retrieved due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def get_keys_from_data_object_range(self, obj_start_data, obj_end_data,
            i_low_comparison_operator, i_high_comparison_operator,
            kstx_parent_transaction, b_return_packed_data):
        """
        Go through the data and find the list of keys from the given values
        in the range of data items. It is assumed that the caller will use
        a type that would support a range of data. Range is
        obj_start_data <= data_item <= obj_end_data.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.get_keys_from_data_object_range)
            list_keys = self.__ksc_index_column.get_data_objects_from_key_range(
                obj_start_data, obj_end_data, i_low_comparison_operator,
                i_high_comparison_operator, kstx_transaction,
                b_return_packed_data)
            kstx_transaction.commit(0, self.get_keys_from_data_object_range)
            return list_keys

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The data from the '
                + u'keys in the data object range could not be retrieved due '
                + u'to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def get_data_objects_from_key_range(self, obj_start_key, obj_end_key,
            i_low_comparison_operator, i_high_comparison_operator,
            kstx_parent_transaction, b_return_packed_data):
        """
        Go through the keys and find the list of data objects from the given
        values in the range of keys. It is assumed that the caller will use
        a type that would support a range of data. Range is
        obj_start_key <= key <= obj_end_key.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.get_data_objects_from_key_range)
            list_data_objects = \
                self.__ksc_primary_column.get_data_objects_from_key_range(
                    obj_start_key, obj_end_key, i_low_comparison_operator,
                    i_high_comparison_operator, kstx_transaction,
                    b_return_packed_data)
            kstx_transaction.commit(0, self.get_data_objects_from_key_range)
            return list_data_objects

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The data from the '
                + u'objects in the key object range could not be retrieved due '
                + u'to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def has_key(self, obj_key, kstx_parent_transaction):
        """
        Return a boolean value that indicates whether or not the key exists
        in the database.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.has_key)
            b_result = self.__ksc_primary_column.has_key(
                obj_key, kstx_parent_transaction)
            kstx_transaction.commit(0, self.has_key)
            return b_result

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'Checking for the '
                + u'key could not be completed due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def has_value(self, obj_value, kstx_parent_transaction):
        """
        Return a boolean value that indicates whether or not the value exists
        at least once in the database.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.has_value)
            b_result = self.__ksc_index_column.has_key(
                obj_value, kstx_transaction)
            kstx_transaction.commit(0, self.has_value)
            return b_result

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'Checking for the '
                + u'value could not be completed due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def insert_item_with_key_value_pair(self, obj_key, obj_data,
                                            kstx_parent_transaction):
        """
        Insert the key/data pair.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.insert_item_with_key_value_pair)
            self.__ksc_primary_column.insert_item_with_key_value_pair(
                obj_key, obj_data, kstx_transaction)

            # Remember, in the index column, the key is the data and the data
            # is the key; this facilitates finding values more quickly at the
            # expense of data duplication.
            self.__ksc_index_column.insert_item_with_key_value_pair(
                obj_data, obj_key, kstx_transaction)

            kstx_transaction.commit(0, self.insert_item_with_key_value_pair)
            return True

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The new key/value '
                + u'pair could not be inserted due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def insert_value_and_generate_key(self, obj_data, kstx_parent_transaction):
        """
        Insert data with time_id as a key. Key will be stored in big endian
        notation.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.insert_value_and_generate_key)
            i_new_id = self.__ksc_primary_column.generate_id()
            self.insert_item_with_key_value_pair(
                i_new_id, obj_data, kstx_parent_transaction)
            kstx_transaction.commit(0, self.insert_value_and_generate_key)
            return True

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The new value with '
                + u'the generated key could not be inserted due to an unknown '
                + u'error.', KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def change_key(self, obj_old_key, obj_new_key, kstx_parent_transaction):
        """
        Get the data from the old key, and store it as a new key/value pair.
        The key column/database must be unique or else this will not work.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.change_key)
            list_primary_key_values = self.__ksc_primary_column.\
                get_data_from_key(obj_old_key, kstx_transaction,
                    KSColumn.B_REGULAR_DATA)
            self.__ksc_primary_column.change_key(
                obj_old_key, obj_new_key, kstx_transaction)

            if (isinstance(list_primary_key_values, list)):
                pass
            else:
                temp = list_primary_key_values
                list_primary_key_values = [temp]

            # For each value in the key column, use that key in the index
            # column to get the values. Delete the "changed key" from the
            # list, add the new value, and re-insert the list.
            for index_key in list_primary_key_values:
                list_index_values = self.__ksc_index_column.get_data_from_key(
                    index_key, kstx_transaction, KSColumn.B_REGULAR_DATA)
                list_index_values.remove(obj_old_key)
                list_index_values.append(obj_new_key)
                self.__ksc_index_column.insert_items_with_key_and_value_list(
                    index_key, list_index_values, kstx_transaction)
            # end loop

            kstx_transaction.commit(0, self.change_key)
            return True

        except ValueError:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The function '
                + u'change_key could not find the primary database key in '
                + u'the index database.',
                KSColumnPlusIndexError.B_CLASS_EXCEPTION)

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The key could not '
                + u'be changed due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def change_all_data_values(self, obj_old_data_value, obj_new_data_value,
                                   kstx_parent_transaction):
        """
        Change all data values to another value. Slow way: scan the database
        and swap out the old values with the new values.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.change_all_data_values)
            print 'primary:'
            self.__ksc_primary_column.print_all(kstx_parent_transaction)
            self.__ksc_primary_column.change_all_data_values(
                obj_old_data_value, obj_new_data_value, kstx_parent_transaction)

            print 'index:'
            self.__ksc_index_column.print_all(kstx_parent_transaction)
            self.__ksc_index_column.change_key(
                obj_old_data_value, obj_new_data_value, kstx_parent_transaction)

            kstx_transaction.commit(0, self.change_all_data_values)
            return True

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The data values '
                + u'could not be changed due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def change_key_value_pair(self, obj_key, obj_data, kstx_parent_transaction):
        """
        Replace the old value of a key with a new value.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.change_key_value_pair)

            # If there is only one value for the key, this operation is
            # allowed. If more than one value exists, there is no way to know
            # what old value to replace.
            list_values = self.__ksc_primary_column.get_data_from_key(
                obj_key, kstx_transaction, KSColumn.B_REGULAR_DATA)

            if (isinstance(list_values, list)):
                pass
            else:
                temp = list_values
                list_values = [temp]

            # Since their is a unique key-value pair, use the replaced value in
            # the primary database to serve as the key for the index database.
            if (len(list_values)==1):
                value_old = list_values[0]
                self.__ksc_primary_column.change_key_value_pair(
                    obj_key, obj_data, kstx_transaction)

                # Retrieve the value list from the index key. Remove the old
                # value key from this list and re-insert the revised list.
                list_of_primary_keys = \
                    self.__ksc_index_column.get_data_from_key(
                        value_old, kstx_transaction, KSColumn.B_REGULAR_DATA)
                list_of_primary_keys.remove(obj_key)
                self.__ksc_index_column.delete_item_by_key(
                    value_old, kstx_transaction)
                self.__ksc_index_column.insert_items_with_key_and_value_list(
                    value_old, list_of_primary_keys, kstx_transaction)

                # Then, use the new value as the key to the index database and
                # add a value of the key from the primary database.
                list_of_primary_keys = \
                    self.__ksc_index_column.get_data_from_key(
                        obj_data, kstx_transaction, KSColumn.B_REGULAR_DATA)

                # It is possible that this data item does not appear in this
                # table at this time; for this reason, there is no old list
                # to delete.
                if (len(list_of_primary_keys)>0):
                    self.__ksc_index_column.delete_item_by_key(
                        obj_data, kstx_transaction)
                # else, nothing

                list_of_primary_keys.append(obj_key)
                self.__ksc_index_column.insert_items_with_key_and_value_list(
                    obj_data, list_of_primary_keys, kstx_transaction)
                kstx_transaction.commit(0, self.change_key_value_pair)
                return True
            else:
                kstx_transaction.cancel()
                KSColumnPlusIndexError.print_and_raise_error(u'The function '
                    + u'change_key_value_pair is only allowed for primary '
                    + u'databases that have a single key-value pair.',
                    KSColumnPlusIndexError.B_CLASS_EXCEPTION)

        except ValueError:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The function '
                + u'change_key_value_pair could not find the primary database '
                + u'key in the index database.',
                KSColumnPlusIndexError.B_CLASS_EXCEPTION)

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The value of the '
                + u'key could not be changed due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def delete_item_by_key(self, obj_key, kstx_parent_transaction):
        """
        Delete the item represented by the given key.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.delete_item_by_key)
            list_primary_key_values = \
                self.__ksc_primary_column.get_data_from_key(
                    obj_key, kstx_transaction, KSColumn.B_REGULAR_DATA)
            self.__ksc_primary_column.delete_item_by_key(
                obj_key, kstx_transaction)

            if (isinstance(list_primary_key_values, list)):
                pass
            else:
                temp = list_primary_key_values
                list_primary_key_values = [temp]
            # end if

            # For each value in the key column, use that key in the index
            # column to get the values. Delete the "changed key" from the
            # list and re-insert the list.
            for index_key in list_primary_key_values:
                list_index_values = self.__ksc_index_column.get_data_from_key(
                    index_key, kstx_transaction, KSColumn.B_REGULAR_DATA)
                list_index_values.remove(obj_key)
                self.__ksc_index_column.delete_item_by_key(
                    index_key, kstx_transaction)
                self.__ksc_index_column.insert_items_with_key_and_value_list(
                    index_key, list_index_values, kstx_transaction)
            # end loop

            kstx_transaction.commit(0, self.delete_item_by_key)
            return True

        except ValueError:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The function '
                + u'delete_item_by_key could not find the primary database '
                + u'key in the index database.',
                KSColumnPlusIndexError.B_CLASS_EXCEPTION)

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The key '
                + u'could not be deleted due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def delete_items_by_value_list(self, list_values, kstx_parent_transaction):
        """
        Delete items having the data values in the list. Slow way: scan the
        database and swap out the old values with the new values.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.delete_items_by_value_list)
            set_primary_keys_to_delete = set([])
            for value in list_values:
                list_primary_keys = self.__ksc_index_column.get_data_from_key(
                    value, kstx_transaction, KSColumn.B_REGULAR_DATA)
                self.__ksc_index_column.delete_item_by_key(
                    value, kstx_transaction)
                set_primary_keys_to_delete = \
                    set_primary_keys_to_delete | set(list_primary_keys)
            # end loop

            for key_primary in set_primary_keys_to_delete:
                self.__ksc_primary_column.delete_item_by_key(
                    key_primary, kstx_transaction)
            # end loop

            kstx_transaction.commit(0, self.delete_items_by_value_list)
            return True

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The items '
                + u'could not be deleted due to an unknown error.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def get_all(self, kstx_parent_transaction, b_return_packed_data):
        """
        Retrieve entire database of items, both keys and values.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.get_all)
            dict_result = self.__ksc_primary_column.get_all(
                kstx_transaction, b_return_packed_data)
            kstx_transaction.commit(0, self.get_all)
            return dict_result

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'A problem occurred '
                + u'while attempting to retrieve all of the data from the '
                + u'column.', KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def values(self, kstx_parent_transaction, b_return_packed_data):
        """
        Retrieve all values from the database of items.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.values)
            list_result = self.__ksc_primary_column.values(
                kstx_transaction, b_return_packed_data)
            kstx_transaction.commit(0, self.values)
            return list_result

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'A problem occurred '
                + u'while attempting to retrieve the values from the column.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def keys(self, kstx_parent_transaction, b_return_packed_data):
        """
        Retrieve all keys from the database of items.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.keys)
            list_result = self.__ksc_primary_column.keys(
                kstx_transaction, b_return_packed_data)
            kstx_transaction.commit(0, self.keys)
            return list_result

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'A problem occurred '
                + u'while attempting to retrieve the keys from the column.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def generate_id(self):
        """
        Generate a new ID for this table.

        """
        return self.__ksc_primary_column.generate_id()


    def print_all(self, kstx_parent_transaction):
        """
        Print all of the values in the primary column and secondary column.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.print_all)
            print 'PRIMARY'
            print '======='
            self.__ksc_primary_column.print_all(kstx_transaction)
            print
            print 'INDEX'
            print '====='
            self.__ksc_index_column.print_all(kstx_transaction)
            kstx_transaction.commit(0, self.print_all)

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'A problem occurred '
                + u'while attempting to print all of the data from the column.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)



    def reorganize_keys(self, kstx_parent_transaction):
        """
        The index column will be greatly affected by the key changes in the
        primary column. In the index column, the value in the primary column
        is the key in the secondary column, and the key in the primary column
        will be the values in the secondary column. So, depending on how many
        keys are changed in the primary column, the secondary column could
        become quite different before this method call. For now, it may actually
        be more efficient to reconstruct the entire secondary column once all
        of the keys in the primary column have been changed.

        """
        try:
            # 1. Reorganize the keys
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.reorganize_keys)
            self.__ksc_primary_column.reorganize_keys(kstx_transaction)

            # 2. Empty out secondary column.
            cursor_index = self.__ksc_index_column.get_cursor(kstx_transaction)
            record_index = cursor_index.first()

            while (record_index):
                packed_key = record_index[0]  # this obtains the key part
                self.__ksc_index_column.delete(packed_key,
                    txn=kstx_transaction.obj_transaction)
                record_index = cursor_index.next()

            # 3. For each key in the primary column, obtain its value and
            #    insert the key value as a value in the secondary column.
            cursor_primary = \
                self.__ksc_primary_column.get_cursor(kstx_transaction)
            record_primary = cursor_primary.first()

            while (record_primary):
                packed_key, pickled_data = record_primary
                self.__ksc_index_column.delete(packed_key,
                    txn=kstx_transaction.obj_transaction)

                u_key_type = self.__ksc_primary_column.u_key_type
                obj_key = self.__ksc_primary_column.unpack_key(
                    packed_key, u_key_type)
                obj_data = self.__ksc_primary_column.unpickle_data(pickled_data)

                # This is not the most efficient way to do things, but it
                # is ok for now just to make sure that things work ok;
                temp = obj_key
                obj_key = obj_data
                obj_data = temp
                self.__ksc_index_column.insert_item_with_key_value_pair(
                    obj_key, obj_data, kstx_transaction)
                record_primary = cursor_primary.next()
            # end loop

            # 4. Commit the transaction
            kstx_transaction.commit(0, self.reorganize_keys)
            return True

        except KSCursorError:
            # Nothing was reorganized because there was nothing to reorganize
            kstx_transaction.commit(0, self.reorganize_keys)
            return True

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'A problem occurred '
                + u'while trying to reorganize the keys.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def return_value_type(self, kstx_parent_transaction):
        """
        Return the data type of the stored value. This may be somewhat
        pointless if binary strings of data are stored. The pickling procedure
        for storing values should retrain the original object type.

        """
        try:
            kstx_transaction = self.get_transaction(kstx_parent_transaction,
                self.return_value_type)

            type_obj = self.__ksc_primary_column.return_value_type(
                kstx_parent_transaction)

            kstx_transaction.commit(0, self.return_value_type)
            return type_obj

        except Exception:
            kstx_transaction.cancel()
            KSColumnPlusIndexError.print_and_raise_error(u'The type of the ' \
                + u'secondary column not be retrieved.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    # These functions are going to pass on specific calls to the underlying
    # KSColumn subclasses to register for their events. Since this column
    # is just an extension of those functions, use the same events. No
    # functions can be registered for the index column since this is
    # redundant because it is a reversal of the primary column (i.e.
    # value-key list instead of key-value).
    def register_before_function_event(self, st_function_name, fn_callback):
        """
        See docstring from KSColumnMapped.

        """
        try:
            return self.__ksc_primary_column.ksem_event_manager.\
                register_before_function_event(
                    st_function_name, fn_callback)
        except KSEventManagerError:
            KSColumnPlusIndexError.print_and_raise_error(u'The "before event" '
                + u'function could not be registered.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def delete_before_function_event(self, st_function_name):
        """
        See docstring from KSColumnMapped.

        """
        try:
            return self.__ksc_primary_column.ksem_event_manager.\
                delete_before_function_event(st_function_name)
        except KSEventManagerError:
            KSColumnPlusIndexError.print_and_raise_error(u'The "before event" '
                + u'function could not be removed.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def register_after_function_event(self, st_function_name, fn_callback):
        """
        See docstring from KSColumnMapped.

        """
        try:
            return self.__ksc_primary_column.ksem_event_manager.\
                register_after_function_event(
                    st_function_name, fn_callback)
        except KSEventManagerError:
            KSColumnPlusIndexError.print_and_raise_error(u'The "after event" '
                + u'function could not be registered.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)


    def delete_after_function_event(self, st_function_name):
        """
        See docstring from KSColumnMapped.

        """
        try:
            return self.__ksc_primary_column.ksem_event_manager.\
                delete_after_function_event(st_function_name)
        except KSEventManagerError:
            KSColumnPlusIndexError.print_and_raise_error(u'The "after event" '
                + u'function could not be removed.',
                KSColumnPlusIndexError.B_UNKNOWN_EXCEPTION)